﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace MathematicallySafe.HeadCounter.Common
{
    // From Bea Stollnitz example at http://bea.stollnitz.com/blog/?p=53

    public class DragDropManager
    {
        public static object DraggedData { get; private set; }

        private FrameworkElement sourceItemContainer;
        private ItemsControl sourceItemsControl;

        private FrameworkElement targetItemContainer;
        private ItemsControl targetItemsControl;
        private FrameworkElement targetItem;

        private DataFormat format = DataFormats.GetDataFormat("DragDropItemsControl");

        private DraggedAdorner draggedAdorner;

        private Window parentWindow;
        private Point initialMousePosition;
        private int dropIndex;

        #region Singleton Pattern

        private static DragDropManager _instance = new DragDropManager();

        public static DragDropManager Instance
        {
            get { return _instance; }
        }

        private DragDropManager() {}

        #endregion

        #region IsDragSource Attached Property

        public static bool GetIsDragSource(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsDragSourceProperty);
        }

        public static void SetIsDragSource(DependencyObject obj, bool value)
        {
            obj.SetValue(IsDragSourceProperty, value);
        }

        public static readonly DependencyProperty IsDragSourceProperty =
            DependencyProperty.RegisterAttached("IsDragSource",
                                                typeof (bool), typeof (DragDropManager),
                                                new UIPropertyMetadata(false, IsDragSourceChanged));

        private static void IsDragSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var dragSource = obj as ItemsControl;
            if (dragSource != null)
            {
                if (Equals(e.NewValue, true))
                {
                    dragSource.PreviewMouseLeftButtonDown += Instance.DragSource_PreviewMouseLeftButtonDown;
                    dragSource.PreviewMouseLeftButtonUp += Instance.DragSource_PreviewMouseLeftButtonUp;
                    dragSource.PreviewMouseMove += Instance.DragSource_PreviewMouseMove;
                }
                else
                {
                    dragSource.PreviewMouseLeftButtonDown -= Instance.DragSource_PreviewMouseLeftButtonDown;
                    dragSource.PreviewMouseLeftButtonUp -= Instance.DragSource_PreviewMouseLeftButtonUp;
                    dragSource.PreviewMouseMove -= Instance.DragSource_PreviewMouseMove;
                }
            }
        }

        #region Drag Source Events

        private void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            sourceItemsControl = (ItemsControl) sender;
            var visual = e.OriginalSource as Visual;

            parentWindow = (Window) Utilities.FindAncestor(typeof (Window), sourceItemsControl);
            initialMousePosition = e.GetPosition(parentWindow);

            sourceItemContainer = Utilities.GetItemContainer(sourceItemsControl, visual);
            if (sourceItemContainer != null)
            {
                DraggedData = sourceItemContainer.DataContext;
            }

            e.Handled = true;
        }

        private void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (DraggedData != null)
            {
                if (IsMovementBigEnough(initialMousePosition, e.GetPosition(parentWindow)))
                {
                    var data = new DataObject(format.Name, DraggedData);

                    bool previousAllowDrop = parentWindow.AllowDrop;
                    parentWindow.AllowDrop = true;
                    parentWindow.DragEnter += ParentWindow_DragEnter;
                    parentWindow.DragOver += ParentWindow_DragOver;
                    parentWindow.DragLeave += ParentWindow_DragLeave;

                    DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject) sender, data, DragDropEffects.Move);

                    RemoveDraggedAdorner();

                    parentWindow.AllowDrop = previousAllowDrop;
                    parentWindow.DragEnter -= ParentWindow_DragEnter;
                    parentWindow.DragOver -= ParentWindow_DragOver;
                    parentWindow.DragLeave -= ParentWindow_DragLeave;

                    DraggedData = null;
                }
            }
        }

        private void DragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            DraggedData = null;
        }

        #endregion

        #endregion

        #region IsDragTarget Attached Property

        public static bool GetIsDropTarget(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsDropTargetProperty);
        }

        public static void SetIsDropTarget(DependencyObject obj, bool value)
        {
            obj.SetValue(IsDropTargetProperty, value);
        }

        public static readonly DependencyProperty IsDropTargetProperty =
            DependencyProperty.RegisterAttached("IsDropTarget", typeof (bool), typeof (DragDropManager),
                                                new UIPropertyMetadata(false, IsDropTargetChanged));

        private static void IsDropTargetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var dropTarget = obj as FrameworkElement;
            if (dropTarget != null)
            {
                if (Equals(e.NewValue, true))
                {
                    dropTarget.AllowDrop = true;

                    dropTarget.PreviewDrop += Instance.DropTarget_PreviewDrop;
                    dropTarget.PreviewDragEnter += Instance.DropTarget_PreviewDragEnter;
                    dropTarget.PreviewDragOver += Instance.DropTarget_PreviewDragOver;
                    dropTarget.PreviewDragLeave += Instance.DropTarget_PreviewDragLeave;
                }
                else
                {
                    dropTarget.AllowDrop = false;

                    dropTarget.PreviewDrop -= Instance.DropTarget_PreviewDrop;
                    dropTarget.PreviewDragEnter -= Instance.DropTarget_PreviewDragEnter;
                    dropTarget.PreviewDragOver -= Instance.DropTarget_PreviewDragOver;
                    dropTarget.PreviewDragLeave -= Instance.DropTarget_PreviewDragLeave;
                }
            }
        }

        #region Drop Target Events

        private void DropTarget_PreviewDragEnter(object sender, DragEventArgs e)
        {
            object draggedItem = e.Data.GetData(format.Name);

            // todo do this properly.
            if (sender is ItemsControl)
            {
                targetItemsControl = sender as ItemsControl;
                targetItem = null;
                DecideDropTarget(e);
            }
            else if (sender is FrameworkElement)
            {
                targetItem = sender as FrameworkElement;
                targetItemsControl = null;
            }

            if (draggedItem != null)
            {
                ShowDraggedAdorner(e.GetPosition(parentWindow));
            }

            e.Handled = true;
        }

        private void DropTarget_PreviewDragOver(object sender, DragEventArgs e)
        {
            object draggedItem = e.Data.GetData(format.Name);

            if (sender is ItemsControl)
            {
                DecideDropTarget(e);
            }

            if (draggedItem != null)
            {
                ShowDraggedAdorner(e.GetPosition(parentWindow));
            }

            e.Handled = true;
        }

        private void DropTarget_PreviewDrop(object sender, DragEventArgs e)
        {
            //object draggedItem = e.Data.GetData(this.format.Name);
            //int indexRemoved = -1;

            //if (draggedItem != null)
            //{
            //    if ((e.Effects & DragDropEffects.Move) != 0)
            //    {
            //        indexRemoved = Utilities.RemoveItemFromItemsControl(this.sourceItemsControl, draggedItem);
            //    }
            //    // This happens when we drag an item to a later position within the same ItemsControl.
            //    if (indexRemoved != -1 && this.sourceItemsControl == this.targetItemsControl && indexRemoved < this.dropIndex)
            //    {
            //        this.dropIndex--;
            //    }
            //    Utilities.InsertItemInItemsControl(this.targetItemsControl, draggedItem, this.dropIndex);

            //    RemoveDraggedAdorner();
            //    RemoveInsertionAdorner();
            //}
            //e.Handled = true;
        }

        private void DropTarget_PreviewDragLeave(object sender, DragEventArgs e)
        {
            //// Dragged Adorner is only created once on DragEnter + every time we enter the window. 
            //// It's only removed once on the DragDrop, and every time we leave the window. (so no need to remove it here)
            //object draggedItem = e.Data.GetData(this.format.Name);

            //if (draggedItem != null)
            //{
            //    RemoveInsertionAdorner();
            //}
            //e.Handled = true;
        }

        private void DecideDropTarget(DragEventArgs e)
        {
            int targetItemsControlCount = targetItemsControl.Items.Count;
            object draggedItem = e.Data.GetData(format.Name);

//            if (IsDropDataTypeAllowed(draggedItem))
//            {
                if (targetItemsControlCount > 0)
                {
                    targetItemContainer = Utilities.GetItemContainer(targetItemsControl, e.OriginalSource as Visual);

                    if (targetItemContainer != null)
                    {
                        var commands = targetItemContainer.CommandBindings;
                        dropIndex = targetItemsControl.ItemContainerGenerator.IndexFromContainer(targetItemContainer);
                    }
                    else
                    {
                        targetItemContainer = targetItemsControl.ItemContainerGenerator.ContainerFromIndex(targetItemsControlCount - 1) as FrameworkElement;
                        dropIndex = targetItemsControlCount;
                    }
                }
                else
                {
                    targetItemContainer = null;
                    dropIndex = 0;
                }
//            }
//            else
//            {
//                targetItemContainer = null;
//                e.Effects = DragDropEffects.None;
//            }
        }

        private bool IsDropDataTypeAllowed(object draggedItem)
        {
            // todo implement this check properly somehow
            return true;
        }

        #endregion

        #endregion

        #region DragDropTemplate Attached Property

        public static DataTemplate GetDragDropTemplate(DependencyObject obj)
        {
            return (DataTemplate) obj.GetValue(DragDropTemplateProperty);
        }

        public static void SetDragDropTemplate(DependencyObject obj, DataTemplate value)
        {
            obj.SetValue(DragDropTemplateProperty, value);
        }

        public static readonly DependencyProperty DragDropTemplateProperty =
            DependencyProperty.RegisterAttached("DragDropTemplate", typeof (DataTemplate), typeof (DragDropManager),
                                                new UIPropertyMetadata(null));

        #endregion

        #region Parent Window Events

        private void ParentWindow_DragEnter(object sender, DragEventArgs e)
        {
            ShowDraggedAdorner(e.GetPosition(parentWindow));
            e.Effects = DragDropEffects.None;
            e.Handled = true;
        }

        private void ParentWindow_DragOver(object sender, DragEventArgs e)
        {
            ShowDraggedAdorner(e.GetPosition(parentWindow));
            e.Effects = DragDropEffects.None;
            e.Handled = true;
        }

        private void ParentWindow_DragLeave(object sender, DragEventArgs e)
        {
            RemoveDraggedAdorner();
            e.Handled = true;
        }

        #endregion

        #region Adorner

        private void ShowDraggedAdorner(Point currentPosition)
        {
            if (draggedAdorner == null)
            {
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(sourceItemsControl);
                draggedAdorner = new DraggedAdorner(DraggedData, GetDragDropTemplate(sourceItemsControl),
                                                    sourceItemContainer, adornerLayer);
            }

            draggedAdorner.SetPosition(currentPosition.X - initialMousePosition.X,
                                       currentPosition.Y - initialMousePosition.Y);
        }

        private void RemoveDraggedAdorner()
        {
            if (draggedAdorner != null)
            {
                draggedAdorner.Detach();
                draggedAdorner = null;
            }
        }

        #endregion

        #region Helpers

        public static bool IsMovementBigEnough(Point initialMousePosition, Point currentPosition)
        {
            return (Math.Abs(currentPosition.X - initialMousePosition.X) >=
                    SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(currentPosition.Y - initialMousePosition.Y) >= SystemParameters.MinimumVerticalDragDistance);
        }

        #endregion
    }
}
